#include <stdio.h>

#include "codegenerator.h"
#include "exceptions.h"

SymbolTable symbolTable;
OperatorMap operatorMap;

string variables = "";
string classprototypes = "";
string prototypes = "";
//string functions = "";
NameStringMap functions;
NameStringMap classes;

NameStringMap freeVars;

vector<ParseTree::Name> functionStack;

string beforeStatement = "";
string afterStatement = "";


// TODO Memory leak when assigning a new object to an existing object (call _destroy)
// TODO Only call destroy when there is no reference to the object left! Use a counter.

// TODO Inheritence + overriding


string printNameStringMap(NameStringMap s)
{
    string ret;
    NameStringMap::iterator it;
    ret = "NameStringMap contains:\n";
    for (it = s.begin() ; it != s.end(); it++)
        ret += printName((*it).first) + " => " + (*it).second + "\n";
    return ret;
}

string printConcatenatedNameStringMap(NameStringMap s)
{
    string ret;
    NameStringMap::iterator it;
    ret = "";
    for (it = s.begin() ; it != s.end(); it++)
        ret += (*it).second + "\n";
    return ret;
}

string generateCode(ParseTree::Thinder &thinder, SymbolTable &s, OperatorMap &m)
{
    string code = "";
    symbolTable = s;
    operatorMap = m;
    
    createOperators();
    
    generateDeclarations(thinder.ns, thinder.declarations);
    // TODO generate function that initializes the global variables
    
    code += "// Prototypes:\n" + classprototypes + "\n" + prototypes;
    code += "\n// Variables:\n" + variables;
    code += "\n// Classes:\n" + printConcatenatedNameStringMap(classes);
    code += "\n// functions:\n" + printConcatenatedNameStringMap(functions);
    
    // if this source contains a main function, create the global main
    ParseTree::Name mainfunc = thinder.ns;
    mainfunc.push_back(ParseTree::Identifier("main"));
    if (symbolTable.find(mainfunc) != symbolTable.end())
    {
        code += "int main(int argc, char* argv[]) {\n";
        code += "printf(\"Application exit code: %d\\n\", *" + symbolTable[mainfunc].internal + "());\n"; // todo arguments
        code += "return 0;}\n";
    }
    return code;
}

ParseTree::Name toName(string str)
{
    ParseTree::Name name;
    while (str.find_first_of(".") != string::npos)
    {
        name.push_back(ParseTree::Identifier(str.substr(0, str.find_first_of("."))));
        str = str.substr(str.find_first_of(".") + 1);
    }
    name.push_back(ParseTree::Identifier(str));
    return name;
}

Symbol findInternal(string internal)
{
    SymbolTable::iterator it;
    for (it = symbolTable.begin() ; it != symbolTable.end(); it++)
        if (((*it).second).internal == internal)
            return (*it).second;
    TException up(15, "Internal symbol " + internal + " was not found");
    throw up;
}

ParseTree::Name findSymbolName(Symbol symbol)
{
    SymbolTable::iterator it;
    for (it = symbolTable.begin() ; it != symbolTable.end(); it++)
        if (((*it).second).internal == symbol.internal)
            return (*it).first;
    TException up(15, "Internal symbol " + symbol.internal + " was not found");
    throw up;
}

bool existsInNamespace(ParseTree::Name ns, ParseTree::Name ident)
{
    try 
    {
        namespaceLookupFQN(ns, ident);
    } catch (TException &e) { 
       return false;
    }
    return true;
}

ParseTree::Name namespaceLookupFQN(ParseTree::Name ns, ParseTree::Identifier ident)
{
    ParseTree::Name name(1, ident);
    return namespaceLookupFQN(ns, name);
}

ParseTree::Name namespaceLookupFQN(ParseTree::Name ns, ParseTree::Name ident)
{
    return namespaceLookupFQN(symbolTable, ns, ident);
}

ParseTree::Name namespaceLookupFQN(SymbolTable &s, ParseTree::Name ns, ParseTree::Name ident)
{    
    SymbolTable::iterator it;
    ParseTree::Name name = ns;
    name.insert(name.end(), ident.begin(), ident.end());
        
    if (ident.size() == 0)
    {
        TException up(5, "Empty name!");
        throw up;
    }
    
    if ((it = s.find(toName(ident.front().identifier))) != s.end()) 
    {
        if ((*it).second.symbolType == Symbol::Link)
        {
            ident.insert(ident.begin(), (*it).second.linkTo.begin(), (*it).second.linkTo.end() - 1);
            name = ident;
            //std::cout << "linked to: " << printName(name) << std::endl;
        }
    }
    
    it = s.find(name);
    try 
    {
        while (it == s.end())
        {            
            if (name.size() == ident.size())
                throw 1;
            int erasesize = 1;
            string del = name[name.size() - (ident.size() + 1)].identifier;
            if (name.size() > ident.size() + 1 && del == name[name.size() - (ident.size() + 2)].identifier)
                erasesize = 2;
            name.erase(name.end() - (ident.size() + erasesize), name.end());
            name.insert(name.end(), ident.begin(), ident.end());
        
            it = s.find(name);
        }
    } catch(int i) {
        //std::cout << printSymbolTable(s) << std::endl;
        TException up(5, "Could not find name \"" + printName(ident) + "\" from namespace \"" + printName(ns) + "\"!");
        throw up;
    }
    
    Symbol symbol = (*it).second;
        
    if (symbol.symbolType == Symbol::Constructor)
    {
        //if ((*it).first[(*it).first.size() - 1].identifier.substr(0, 1) == "%") 
        //    name.pop_back();
        if (ident.size() < 2 || ident[ident.size() - 1] != ident[ident.size() - 2])
        {
            name.pop_back();
        }
    }
    
    return name;
}

Symbol namespaceLookup(ParseTree::Name ns, ParseTree::Identifier ident)
{
    ParseTree::Name name(1, ident);
    return namespaceLookup(ns, name);
}

Symbol namespaceLookup(ParseTree::Name ns, ParseTree::Name ident)
{
    return symbolTable[namespaceLookupFQN(ns, ident)];
}

bool isFieldOfClass(ParseTree::Name ns, ParseTree::Identifier ident)
{
    ParseTree::Name name(1, ident);
    return isFieldOfClass(ns, name);
}

bool isFieldOfClass(ParseTree::Name ns, ParseTree::Name ident)
{    
    ParseTree::Name pField = namespaceLookupFQN(ns, ident);
    ParseTree::Name pClass = pField;
    pClass.pop_back();
        
    return symbolTable[pField].symbolType == Symbol::Var && symbolTable[pClass].symbolType == Symbol::Class;
}

bool isMethodOfClass(ParseTree::Name ns, ParseTree::Identifier ident)
{
    ParseTree::Name name(1, ident);
    return isFieldOfClass(ns, name);
}

bool isMethodOfClass(ParseTree::Name ns, ParseTree::Name ident)
{    
    ParseTree::Name pField = namespaceLookupFQN(ns, ident);
    ParseTree::Name pClass = pField;
    pClass.pop_back();
        
    return symbolTable[pField].symbolType == Symbol::Func && symbolTable[pClass].symbolType == Symbol::Class;
}

ParseTree::Name getThisClass(ParseTree::Name ns)
{
    try 
    {
        while (symbolTable[ns].symbolType != Symbol::Class)
        {
            if (ns.size() == 1)
                throw 1;
            ns.erase(ns.end() - 1, ns.end());
        }
    } catch(int i) {
        TException up(5, "Expected to be in a class, but no class was found in this namespace \"" + printName(ns) + "\"!");
        throw up;
    }
    return ns;
}

bool isFieldOfThisClass(ParseTree::Name ns, ParseTree::Identifier ident)
{
    ParseTree::Name name(1, ident);
    return isFieldOfThisClass(ns, name);
}

bool isFieldOfThisClass(ParseTree::Name ns, ParseTree::Name ident)
{
    if (!isFieldOfClass(ns, ident))
        return false;
        
    ParseTree::Name thisClass;
    try 
    {
        thisClass = getThisClass(ns);
    } catch (TException &e) {
        return false;
    }
    
    ident = namespaceLookupFQN(ns, ident);
    ident.erase(ident.end() - 1, ident.end());
    return thisClass == ident;
}

bool isMethodOfThisClass(ParseTree::Name ns, ParseTree::Identifier ident)
{
    ParseTree::Name name(1, ident);
    return isFieldOfThisClass(ns, name);
}

bool isMethodOfThisClass(ParseTree::Name ns, ParseTree::Name ident)
{
    if (!isMethodOfClass(ns, ident))
        return false;
        
    ParseTree::Name thisClass;
    try 
    {
        thisClass = getThisClass(ns);
    } catch (TException &e) {
        return false;
    }
    
    ident = namespaceLookupFQN(ns, ident);
    ident.erase(ident.end() - 1, ident.end());
    return thisClass == ident;
}

string generateType(ParseTree::Name ns, ParseTree::Type &type)
{
    string code = "";
    if (type.typeType == ParseTree::Type::PrimitiveType)
    {
        switch (type.primitiveType)
        {
            case ParseTree::Type::Int:
                code += "int";
                break;
            case ParseTree::Type::Float:
                code += "float";
                break;
            case ParseTree::Type::Bool:
                code += "short int";
                break;
            case ParseTree::Type::Byte:
            case ParseTree::Type::Char: // TODO wchar_t
                code += "char";
                break;
            case ParseTree::Type::Long:
                code += "long int";
                break;
            case ParseTree::Type::Double:
                code += "double";
                break;
            case ParseTree::Type::Short:
                code += "short int";
                break;
        }
    } else if (type.typeType == ParseTree::Type::UserType)
    {
        code += namespaceLookup(ns, type.userType).internal;
    }
    return code;
}

string generateFormPars(ParseTree::Name ns, vector<ParseTree::FormPar> &formPars)
{
    string s = "";
    for(unsigned int i = 0; i < formPars.size(); i++)
    {
        s += generateFormPar(ns, formPars[i]);
        if (i < formPars.size() - 1)
            s += ", ";
    }
    return s;
}

string generateFormPar(ParseTree::Name ns, ParseTree::FormPar &formPar)
{
    string s = generateType(ns, formPar.type) + "* ";
    ParseTree::Name name = ns;
    name.push_back(formPar.identifier);
    s += symbolTable[name].internal;
            
    // TODO: Initialization
            
    return s;
}

ParseTree::Type getPrimaryPostType(ParseTree::Name ns, ParseTree::Type &primaryType, ParseTree::PrimaryPost pp)
{
    ParseTree::Type type;
    vector<ParseTree::IdentRest> irest = primaryPostToIdentRest(pp);

    if (pp.ppType == ParseTree::PrimaryPost::Empty)
        return primaryType;

    if (primaryType.typeType == ParseTree::Type::PrimitiveType)
    {
        if (primaryType.primitiveType == ParseTree::Type::Int) 
        {
            primaryType.userType = toName("Thinder.Prim.Integer");
            primaryType.typeType = ParseTree::Type::UserType;        
        }
        // TODO: werkt dit?
        
        // TODO: other primitives
    }

    ParseTree::Name name = primaryType.userType;
            
    name = namespaceLookupFQN(ns, name);
    Symbol s = symbolTable[name];
    // TODO arrays (dereferenced vs array types)
    for (unsigned int i = 0; i < irest.size(); i++)
    {
        if (irest[i].identRestType == ParseTree::IdentRest::Ident)
        {
            ParseTree::Name newName = s.type.userType;
            newName.push_back(*irest[i].identifier);
            newName = namespaceLookupFQN(ns, newName);
            s = symbolTable[newName];
        }
    }
    
    return s.type;    
}

vector<string> generateOpExprLayout(ParseTree::Name ns, ParseTree::OpExpr *oe, map<string, ParseTree::Unary*> &unaries, int i)
{
    vector<string> tokens;
    char is[16] = "";
    
    if (oe->opExprType == ParseTree::OpExpr::Expr)
    {
        sprintf(is, "%d", i++);
        unaries[string(is)] = oe->unary;
        tokens.push_back(string(is));
        tokens.push_back(oe->op->op);
        vector<string> newTokens = generateOpExprLayout(ns, oe->next, unaries, i);
        //tokens.push_back("(");
        tokens.insert(tokens.end(), newTokens.begin(), newTokens.end());
        //tokens.push_back(")");
    }
    else if (oe->opExprType == ParseTree::OpExpr::Single)
    {
        sprintf(is, "%d", i++);
        unaries[string(is)] = oe->unary;
        tokens.push_back(string(is));
    }
    else if (oe->opExprType == ParseTree::OpExpr::Empty)
    {
        
    }
    
    return tokens;
}

ParseTree::Expression *generateLayoutExpr(vector<string> tokens, map<string, ParseTree::Unary*> &unaries)
{
    // Tokens may only contain sanitized expressions (sanitized = all operators are parenthesized)
    
    /*std::cout << "generateLayoutExpr.tokens = ";
    for (unsigned int it = 0; it < tokens.size(); it++)
        std::cout << tokens[it] << " ";
    std::cout << std::endl;     */   
               
       
    if (tokens[0].compare("(") == 0 && tokens[tokens.size() - 1].compare(")") == 0) 
    {
        tokens.erase(tokens.begin());
        tokens.pop_back();
    }
        
    ParseTree::Unary *unaryA = NULL;
    ParseTree::Unary *unaryB = NULL;
    string op = "";
    
    for (unsigned int i = 0; i < tokens.size(); i++)
    {
        // unaries are being created
        //TODO: at operator initialize OpExpr, and at unary-identifier or new unary finish it
        //      and put it into an Expression to return.
        
        if (tokens[i].compare("(") == 0) 
        {
            ParseTree::Unary *unary;
            if (unaryA == NULL) {
                unaryA = new ParseTree::Unary;
                unary = unaryA;
            } else {
                unaryB = new ParseTree::Unary;
                unary = unaryB;
            }
                
            unsigned int open = 1;
            unsigned int n;
            //std::cout << "tokens size: " << tokens.size() << std::endl;
            for (n = i + 1; n < tokens.size(); n++)
            {
                if (tokens[n].compare("(") == 0)
                    open++;
                if (tokens[n].compare(")") == 0)
                    open--;
                if (open == 0)
                    break;
            }
            
            if (tokens[n].compare(")") == 0 && n == tokens.size() - 1)
                n--;
            
            if (open > 0) 
            {
                TException up(54, "Expression is not complete (missing `)'-character)");
                throw up;
            }
            
            //std::cout << "n: " << n << ", i: " << i << std::endl;
            
            vector<string> newTokens;
            newTokens.insert(newTokens.end(), tokens.begin() + i + 1, tokens.begin() + i + n - 1);
            
            
           /* std::cout << "newTokens = ";
    for (unsigned int it = 0; it < newTokens.size(); it++)
        std::cout << newTokens[it] << " ";
    std::cout << std::endl;   */     
    
            
            // new Unary
            ParseTree::Primary primary;
            ParseTree::PrimaryPost primaryPost;
            ParseTree::PrimaryExpression primaryExpression;
            primaryPost.ppType = ParseTree::PrimaryPost::Empty;
            primaryPost.next = NULL;
            primary.primaryPost = primaryPost;
            primaryExpression.peType = ParseTree::PrimaryExpression::Expr;
            primaryExpression.expression = generateLayoutExpr(newTokens, unaries);
            primary.primaryExpression = primaryExpression;
            unary->primary = primary;
            
            i = n;
        } else if (unaries.find(tokens[i]) != unaries.end())
        {
            // unary token
            if (unaryA == NULL)
                unaryA = unaries[tokens[i]];
            else
                unaryB = unaries[tokens[i]];
        } else {
            // operator token
            op = tokens[i];
        }
        
        if (unaryA != NULL && unaryB != NULL && op.compare("") != 0) 
        {
            // finished searching
            
            ParseTree::Expression *expression = NULL;
            ParseTree::OpExpr *opexprA = NULL;
            ParseTree::OpExpr *opexprB = NULL;
    
            opexprA = new ParseTree::OpExpr;
            opexprA->opExprType = ParseTree::OpExpr::Expr;
            opexprA->unary = unaryA;
            opexprA->op = new ParseTree::Operator;
            opexprA->op->op = op;
            
            opexprB = new ParseTree::OpExpr;
            opexprB->opExprType = ParseTree::OpExpr::Single;
            opexprB->unary = unaryB;
            opexprB->next = NULL;
            
            opexprA->next = opexprB;
            
            expression = new ParseTree::Expression;
            expression->exprType = ParseTree::Expression::ExprOp;
            expression->opExpr = opexprA;
            
            return expression;
        }
    }
      
    TException up(55, "Expression is not complete");
    throw up;     
}

ParseTree::Name getDominantOperatorOverload(ParseTree::Name ns, ParseTree::Expression &expr)
{
    vector<string> tokens;
    map<string, ParseTree::Unary*> unaries;
    
    tokens = generateOpExprLayout(ns, expr.opExpr, /* out */unaries, 0);
    
    /*for (unsigned int it = 0; it < tokens.size(); it++)
        std::cout << tokens[it] << " ";
    std::cout << "    ";  */ 
    
    processOperators_raw(tokens);
    // NOTE: Unaries may itself contain an expression
    
    /*for (unsigned int it = 0; it < tokens.size(); it++)
        std::cout << tokens[it] << " ";
    std::cout << std::endl;     */       
    
    // nu is tokens een lijst met nummers die unaries zijn en alle operators
    // zijn omringd met haken zodat de volgorde niet meer onduidelijk is.
    
    // find the token that is the most dominant in this expression
    string op = " ";
    int opPos = -1;
    //op pos is  3  of  n - 3
    if (tokens[1].compare("(") == 0)
        opPos = tokens.size() - 3;
    else
        opPos = 2;
    
    op = tokens[opPos];
    
    //std::cout << "found dominant operator: " << op << ", " << opPos << std::endl;
    
    // `op`    is nu de dominante operator
    // `opPos` is de plek waar die operator in tokens staat
    
    // Genereer een ParseTree::Expression voor de lhs en de rhs
    
    ParseTree::Expression lhs, rhs;
    lhs.exprType = ParseTree::Expression::ExprOp;
    rhs.exprType = ParseTree::Expression::ExprOp;
    ParseTree::OpExpr opexpr1;
    ParseTree::OpExpr opexpr2;
    
    ParseTree::Expression *exprRef;
    
    if (opPos == 2) {
        opexpr1.opExprType = ParseTree::OpExpr::Single;
        opexpr1.unary = unaries[tokens[opPos - 1]];
        opexpr1.next = NULL;
        lhs.opExpr = &opexpr1;
        exprRef = &rhs;
    } else {
        opexpr1.opExprType = ParseTree::OpExpr::Single;
        opexpr1.unary = unaries[tokens[opPos + 1]];
        opexpr1.next = NULL;
        rhs.opExpr = &opexpr1;
        exprRef = &lhs;
    }
    
    if (opPos == 2 && tokens[3].compare("(") != 0)
    {
        // 2 unaries (a `op` b)
        opexpr2.opExprType = ParseTree::OpExpr::Single;
        opexpr2.unary = unaries[tokens[opPos + 1]];
        opexpr2.next = NULL;
        rhs.opExpr = &opexpr2;
    } else {
        // a `op` (expr)   or   (expr) `op` a
        int open = 0;            
        int posStart = -1;
        int posStop = -1;
        for (unsigned int i = 1; i < tokens.size() - 1; i++)
        {
            if (tokens[i].compare("(") == 0) 
            {
                open++;
                if (posStart == -1)
                    posStart = i;
                continue;
            }
            
            if (tokens[i].compare(")") == 0) 
            {
                open--;
                if (open == 0)
                {
                    posStop = i;
                    break;
                }
                continue;
            }                
        }
        
        if (posStart == -1 || posStop == -1) {
            TException up(6, "Haakjes error!");
            throw up;
        }
        
        vector<string> tokenstmp;
        tokenstmp.insert(tokenstmp.begin(), tokens.begin() + posStart + 1, tokens.begin() + posStop);
        ParseTree::Expression *tmpExpr = generateLayoutExpr(tokenstmp, unaries);
        exprRef->opExpr = tmpExpr->opExpr;
    }
    
    ParseTree::Type lhsType = getTypeOfExpression(ns, lhs);
    ParseTree::Type rhsType = getTypeOfExpression(ns, rhs);
    
    //std::cout << printType(lhsType) << ", " << printType(rhsType) << std::endl;
    
    // vind overload van operator
    ParseTree::Declaration declaration;
    declaration.declType = ParseTree::Declaration::OperatorDecl;
    declaration.type = ParseTree::Type::Unknown();
    vector<ParseTree::FormPar> argumentTypes;
    
    ParseTree::FormPar fp1;
    fp1.type = lhsType;
    argumentTypes.push_back(fp1);
    
    ParseTree::FormPar fp2;
    fp2.type = rhsType;
    argumentTypes.push_back(fp2);
    
    declaration.formPars = argumentTypes;
    return findOverload(ns, toName(op), declaration);
}

ParseTree::Type getTypeOfExpression(ParseTree::Name ns, ParseTree::Expression &expr)
{
    ParseTree::Type type;
    
    if (expr.exprType == ParseTree::Expression::ExprAss)
    {
        ParseTree::Name name(1, *expr.assignment->designator.identifier);
        //ParseTree::Name identRest = generateIdentRest(ns, expr.assignment->designator.rest);
        //name.insert(name.end(), identRest.begin(), identRest.end());    
        Symbol s = namespaceLookup(ns, name);
        return s.type;
    }
    else if (expr.exprType == ParseTree::Expression::ExprOp)
    {
        if (expr.opExpr->opExprType == ParseTree::OpExpr::Single)
        {
            ParseTree::PrimaryExpression pe = expr.opExpr->unary->primary.primaryExpression;
            if (pe.peType == ParseTree::PrimaryExpression::Literal && expr.opExpr->unary->primary.primaryPost.ppType == ParseTree::PrimaryPost::Empty)
            {
                if (pe.literal->literalType == ParseTree::Literal::Int)
                {
                    type.typeType = ParseTree::Type::PrimitiveType;
                    type.primitiveType = ParseTree::Type::Int;
                }
                if (pe.literal->literalType == ParseTree::Literal::Float)
                {
                    type.typeType = ParseTree::Type::PrimitiveType;
                    type.primitiveType = ParseTree::Type::Float;
                }
                if (pe.literal->literalType == ParseTree::Literal::String)
                {
                    type.typeType = ParseTree::Type::UserType;
                    type.userType = toName("Thinder.String");
                }
                if (pe.literal->literalType == ParseTree::Literal::Char)
                {
                    type.typeType = ParseTree::Type::PrimitiveType;
                    type.primitiveType = ParseTree::Type::Char;
                }
                
                return type;
            } else if (pe.peType == ParseTree::PrimaryExpression::Literal && expr.opExpr->unary->primary.primaryPost.ppType != ParseTree::PrimaryPost::Empty)
            {
                if (pe.literal->literalType == ParseTree::Literal::Int)
                {
                    type.typeType = ParseTree::Type::UserType;
                    type.userType = toName("Thinder.Prim.Integer");
                }
                if (pe.literal->literalType == ParseTree::Literal::Float)
                {
                    type.typeType = ParseTree::Type::UserType;
                    type.userType = toName("Thinder.Prim.Float");
                }
                if (pe.literal->literalType == ParseTree::Literal::String)
                {
                    type.typeType = ParseTree::Type::UserType;
                    type.userType = toName("Thinder.String");
                }
                if (pe.literal->literalType == ParseTree::Literal::Char)
                {
                    type.typeType = ParseTree::Type::UserType;
                    type.userType = toName("Thinder.Prim.Character");
                }
            } else if (pe.peType == ParseTree::PrimaryExpression::Ident) 
            {
                type = symbolTable[namespaceLookupFQN(ns, *pe.identifier)].type;
            } else if (pe.peType == ParseTree::PrimaryExpression::Expr) 
            {
                type = getTypeOfExpression(ns, *pe.expression);
            } else if (pe.peType == ParseTree::PrimaryExpression::Cast) 
            {
                // TODO check if this is the right behaviour
                return *pe.type;
            } else if (pe.peType == ParseTree::PrimaryExpression::New) 
            {
                type.typeType = ParseTree::Type::UserType;
                type.userType = namespaceLookupFQN(ns, pe.name);
            }
            type = getPrimaryPostType(ns, type, expr.opExpr->unary->primary.primaryPost);
        }
        if (expr.opExpr->opExprType == ParseTree::OpExpr::Expr)
        {
            type = symbolTable[getDominantOperatorOverload(ns, expr)].type;  
        }
        if (expr.opExpr->opExprType == ParseTree::OpExpr::Empty)
        {
            std::cerr << "Warning: Empty expression" << std::endl;
        }
        
    }
    
    return type;
}







